package websocket

import (
	"context"
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/url"
	"peilian-api/app/global/log"
	variable "peilian-api/app/global/variable"
	"peilian-api/utils/tools"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/net/websocket"
)

const (
	Host   = "rtasr.xfyun.cn/v1/ws"
	AppID  = "c01ca6f9"
	AppKey = "def21fdfa59f4353c061b7041084cdaa"
	EndTag = "{\"end\": true}"
)

type AsrResult struct {
	Cn    Cn      `json:"cn"`
	SegId float64 `json:"seg_id"`
	Ls    bool    `json:"ls"`
}

type Cn struct {
	St St `json:"st"`
}

type St struct {
	Bg   string      `json:"bg"`
	Ed   string      `json:"ed"`
	Type string      `json:"type"`
	Rt   []RtElement `json:"rt"`
}

type RtElement struct {
	Ws []WsElement `json:"ws"`
}

type WsElement struct {
	Wb float64     `json:"wb"`
	We float64     `json:"we"`
	Cw []CwElement `json:"cw"`
}

type CwElement struct {
	W  string `json:"w"`
	Wp string `json:"wp"`
}

type WsInfo struct {
	Bg   string  `json:"bg"`
	Ed   string  `json:"ed"`
	Wb   float64 `json:"wb"`
	We   float64 `json:"we"`
	Word string  `json:"word"`
}

type WsText struct {
	Bg   string `json:"bg"`
	Ed   string `json:"ed"`
	Wi   []WsInfo
	Text string `json:"text"`
}

// 记录时间
type WsRecord struct {
	Begin float64 `json:"begin"`
	End   float64 `json:"end"`
}

func ConnServer(ctx *gin.Context, audioCh chan []byte, textCh chan string) {
	ts := strconv.FormatInt(time.Now().Unix(), 10)
	mac := hmac.New(sha1.New, []byte(variable.Config.WS.AppKey))
	strByte := []byte(variable.Config.WS.AppID + ts)
	strMd5Byte := md5.Sum(strByte)
	strMd5 := fmt.Sprintf("%x", strMd5Byte)
	mac.Write([]byte(strMd5))
	signa := url.QueryEscape(base64.StdEncoding.EncodeToString(mac.Sum(nil)))
	requestParam := "appid=" + variable.Config.WS.AppID + "&ts=" + ts + "&signa=" + signa

	conn, err := websocket.Dial("ws://"+Host+"?"+requestParam, websocket.SupportedProtocolVersion, "http://"+Host)
	if err != nil {
		msg := fmt.Sprintf("连接讯飞asr服务错误: %s", err.Error())
		log.Logger.ErrorMsgF(ctx, msg)
		// 重试3次
		var count = 0
		for i := 0; i < 3; i++ {
			conn, err = websocket.Dial("ws://"+Host+"?"+requestParam, websocket.SupportedProtocolVersion, "http://"+Host)
			if err != nil {
				count++
				continue
			}
			break
		}
		if count == 3 {
			log.Logger.ErrorMsgF(ctx, "连接讯飞asr服务-未重试成功,该场面是失败")
			return
		}
		log.Logger.InfoMsgF(ctx, "连接讯飞asr服务-重试成功")
	}

	var message string
	err = websocket.Message.Receive(conn, &message)
	if err != nil {
		msg := fmt.Sprintf("讯飞-websocket握手错误: %s", err.Error())
		log.Logger.ErrorMsgF(ctx, msg)
		return
	}
	var m map[string]string
	err = json.Unmarshal([]byte(message), &m)
	log.Logger.InfoF(ctx, message)
	if err != nil {
		msg := fmt.Sprintf("讯飞-握手数据格式错误: %s", err.Error())
		log.Logger.ErrorMsgF(ctx, msg)
		return
	} else if m["code"] != "0" { // 讯飞连接错误
		msg := fmt.Sprintf("讯飞-websocket握手失败: %s", message)
		log.Logger.ErrorMsgF(ctx, msg)
		return
	}

	defer func() {
		err := conn.Close()
		if err != nil {
			msg := fmt.Sprintf("讯飞-websocket连接断开异常: %s", err.Error())
			log.Logger.WarnMsgF(ctx, msg)
		}
	}()
	sendChan := make(chan int, 1)
	readChan := make(chan int, 1)
	defer close(sendChan)
	defer close(readChan)
	go func() {
		defer tools.RecoverGoroutineCtx(ctx)
		send(ctx, conn, sendChan, audioCh, textCh)
	}()
	receiveCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
	defer cancel()
	go func() {
		defer tools.RecoverGoroutineCtx(ctx)
		receive(receiveCtx, conn, readChan, textCh)
	}()
	<-sendChan
	select {
	case <-readChan:
	case <-receiveCtx.Done():
		textCh <- "EOFS"
	}
}

func send(ctx *gin.Context, conn *websocket.Conn, sendChan chan int, audioCh chan []byte, textCh chan string) {
	// 分片上传音频
	defer func() { sendChan <- 1 }()
	for {
		audio := <-audioCh

		if string(audio) == "AUDIO::FINISH" {
			break

		} else if string(audio) == "AUDIO::SPECIAL::FINISH" {
			break
		} else if string(audio) == "AUDIO::PANIC" {
			log.Logger.DebugF(ctx, "前端断开ws连接")
			break
		}
		if err := websocket.Message.Send(conn, audio); err != nil {
			//ConnServer(ctx, audioCh, textCh) // 重连
			log.Logger.ErrorF(ctx, fmt.Sprintf("上传二进制音频失败,重新连接, %s, audio: %s", err.Error(), string(audio)))
			websocket.Message.Send(conn, audio) // 重试连接，不知道重新开启的通道是否有用

		}
		time.Sleep(40 * time.Millisecond)
	}

	// 上传结束符
	if err := websocket.Message.Send(conn, EndTag); err != nil {
		log.Logger.ErrorF(ctx, "上传结束字符串失败:"+err.Error())
	} else {
		log.Logger.InfoF(ctx, "上传结束字符串成功")
	}
}

func receive(ctx context.Context, conn *websocket.Conn, readChan chan int, textCh chan string) {
	defer func() { readChan <- 1 }()
	for {
		var msg []byte
		var result map[string]string
		if err := websocket.Message.Receive(conn, &msg); err != nil {
			if err.Error() == "EOF" {
				log.Logger.InfoF(ctx, "收到文本结果: EOF; "+"数据接收完成")
			} else {
				log.Logger.ErrorF(ctx, "接受数据错误: "+err.Error())
			}
			break
		}

		err := json.Unmarshal(msg, &result)
		if err != nil {
			log.Logger.ErrorF(ctx, "response json parse error, "+string(msg))
			continue
		}

		if result["code"] == "0" {
			var asrResult AsrResult
			err := json.Unmarshal([]byte(result["data"]), &asrResult)
			if err != nil {
				log.Logger.ErrorF(ctx, "parse asrResult error: "+err.Error())
				log.Logger.InfoF(ctx, "receive msg: "+string(msg))
				break
			}
			if asrResult.Cn.St.Type == "0" {
				// 最终结果
				var text string
				var wsText = WsText{Bg: asrResult.Cn.St.Bg, Ed: asrResult.Cn.St.Ed}
				for _, wse := range asrResult.Cn.St.Rt[0].Ws {
					var t = ""
					for _, cwe := range wse.Cw {
						println("cwe.W: ", cwe.W)
						text = text + cwe.W
						t += cwe.W
					}
					var winfo = WsInfo{Bg: asrResult.Cn.St.Bg, Ed: asrResult.Cn.St.Ed, Wb: wse.Wb, We: wse.We, Word: t}
					wsText.Wi = append(wsText.Wi, winfo)
				}
				wsText.Text = text
				b, _ := json.Marshal(wsText)
				textCh <- string(b) // 将数据进行封装
			}
			if asrResult.Ls {
				log.Logger.InfoF(ctx, "收到讯飞的结束消息")
				textCh <- "EOFS"
				break
			}
		} else {
			log.Logger.WarnF(ctx, "invalid result: "+string(msg))
		}
	}
}
